#include <iostream>
#include "unistd.h"
extern "C" {
#include "libavutil/log.h"
#include "libavformat/avio.h"
#include "libavformat/avformat.h"
}

static char errorBuf[1024] = {0};
static char *avGetError(int errNum) {
    av_strerror(errNum, errorBuf, 1024);
    return errorBuf;
}

int main(int argc, char *argv[])
{
    AVFormatContext *inputFmtCxt = NULL;
    int videoIndex = -1;
    AVPacket *packet = NULL;
    int ret = -1;

    if (argc < 3) {
        std::cout << "usage inputFile outputFile." << std::endl;
        return -1;
    }

    FILE *outFp = fopen(argv[2], "wb");
    std::cout << "inputFile: " << argv[1] << " outputFile: " << argv[2] << std::endl;

    //分配解复用器的内存，使用 avformat_close_input 释放
    inputFmtCxt = avformat_alloc_context();
    if (inputFmtCxt == NULL) {
        std::cout << "[Error] Could not allocate context." << std::endl;
        return -1;
    }

    //根据url打开码流，选择匹配的解复用器
    ret = avformat_open_input(&inputFmtCxt, argv[1], NULL, NULL);
    if (ret != 0) {
        std::cout << "[Error] avformat_open_input: " << avGetError(ret) << std::endl;
        return -1;
    }

    //读取媒体文件的部分数据包以获取码流信息
    ret = avformat_find_stream_info(inputFmtCxt, NULL);
    if (ret < 0) {
        std::cout << "[Error] avformat_find_stream_info: " << avGetError(ret) << std::endl;
        avformat_close_input(&inputFmtCxt);
        return -1;
    }

    //查找出视频流
    videoIndex = av_find_best_stream(inputFmtCxt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoIndex == -1) {
        std::cout << "Didn't find the video stream." << std::endl;
        avformat_close_input(&inputFmtCxt);
        return -1;
    }

    //分配数据包
    packet = av_packet_alloc();
    av_init_packet(packet);

    /*1. 获取相应的比特流过滤器
     * flv/mp4/mkv 等结构中，h264 需要 h264 mp4toannexb 处理。添加 SPS/PPS 等信息。
     * flv 封装时，可以把多个 NALU 放在一个 VIDEO TAG 中，结构为 4B NALU 长度+ NALU1 + 4B NALU 长度 + NALU2 + ...，
     * 需要做的处理是把 4B 长度换成 00000001或者000001
     */
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext *bsfCtx = NULL;
    //2. 初始化过滤器上下文
    av_bsf_alloc(bsfilter, &bsfCtx);
    //3. 添加编码器属性
    avcodec_parameters_copy(bsfCtx->par_in, inputFmtCxt->streams[videoIndex]->codecpar);
    av_bsf_init(bsfCtx);

    while (true) {
        ret = av_read_frame(inputFmtCxt, packet);
        if (ret == 0) {
#if 0
            if (packet->stream_index == videoIndex) {
                int inputSize = packet->size;
                int outputPacketCount = 0;

                //bitstreamfilter 内部去维护空间
                if (av_bsf_send_packet(bsfCtx, packet) != 0) {
                    av_packet_unref(packet);
                    continue;
                }

                av_packet_unref(packet);

                while (av_bsf_receive_packet(bsfCtx, packet) == 0) {
                    outputPacketCount++;
                    size_t size = fwrite(packet->data, 1, packet->size, outFp);
                    if (size != packet->size) {
                        std::cout << "fwrite failed. Write: " << size << " packet size: " << packet->size << std::endl;
                    }
                }

                if (outputPacketCount >= 2)
                    std::cout << "cur packet(size:" << inputSize << " only get 1 out packet, it get " << outputPacketCount << " packets." << std::endl;
            }
#else
            //Ts 流可以直接写入
            size_t size = fwrite(packet->data, 1, packet->size, outFp);
            if (size != packet->size)
                std::cout << "fwrite failed. Write: " << size << " packet size: " << packet->size << std::endl;

            av_packet_unref(packet);
#endif
            //释放内存
            av_packet_unref(packet);
        }
        else {
            std::cout << "Error: " << avGetError(ret) << std::endl;
            break;
        }
    }

    if (outFp != NULL)
        fclose(outFp);

    if (bsfCtx != NULL)
        av_bsf_free(&bsfCtx);

    if (packet != NULL)
        av_packet_free(&packet);

    if (inputFmtCxt != NULL)
        avformat_close_input(&inputFmtCxt);

    std::cout << "Finished" << std::endl;

    return 0;
}
